`include "define.svh"

module ex_wrap(
    input wire                              clk,
    input wire                              rstn,
    input wire                              id_stalled,
    input wire                              ctrl_stall,
    input wire                              pipeline_flush_i,
    input wire                              addr_ok,
    input wire                              data_ok,
    
    input wire [`PC_WIDTH - 1 : 0]          debug_pc_i,         // clk
    input wire                              pc_in_delay_slot_i, // clk
    input wire [`EXCCODE_WIDTH - 1 : 0]     exc_code_i,         // clk
    input wire [`PC_WIDTH - 1 : 0]          error_pc_i,         // clk
    
    input wire [`REG_WIDTH - 1 : 0]         opprand1_i,         // clk
    input wire [`REG_WIDTH - 1 : 0]         opprand2_i,         // clk
    input wire [`ALUOP_WIDTH - 1 : 0]       aluop_i,            // clk
    
    input wire [`REG_ADDR_WIDTH - 1 : 0]    wregaddr_i,         // clk
    input wire                              wregenable_i,       // clk
    
    input wire                              id_whien_i,
    input wire                              id_wloen_i,
    input wire [`REG_WIDTH - 1 : 0]         id_whilodata_i,
    
    output reg [`PC_WIDTH - 1 : 0]          debug_pc_o,
    output reg                              pc_in_delay_slot_o,
    output reg [`EXCCODE_WIDTH - 1 : 0]     exc_code_o,
    output reg [`PC_WIDTH - 1 : 0]          badvaddr_o,
    
    output reg [`REG_ADDR_WIDTH - 1 : 0]    wregaddr_o,
    output reg                              wregenable_o,
    output reg [`REG_WIDTH - 1 : 0]         wregdata_o,
    output reg [`REG_WIDTH - 1 : 0]         whireg_o,
    output reg [`REG_WIDTH - 1 : 0]         wloreg_o,
    output reg                              whien_o,
    output reg                              wloen_o,
    output reg [`REG_WIDTH - 1 : 0]         data_o,
    
    input wire [3 : 0]                      rdataen_i,      // clk
    input wire                              rdatasigned_i,  // clk
    input wire [3 : 0]                      wdataen_i,      // clk
    input wire [`REG_WIDTH - 1 : 0]         wdata_i,        // clk
    
    input wire                              cp0_wen_i,      // clk  
    input wire [`REG_ADDR_WIDTH - 1 : 0]    cp0_waddr_i,    // clk
    input wire [`REG_WIDTH - 1 : 0]         cp0_wdata_i,    // clk
    
    output reg                              cp0_wen_o,
    output reg [`REG_ADDR_WIDTH - 1 : 0]    cp0_waddr_o,
    output reg [`REG_WIDTH - 1 : 0]         cp0_wdata_o,
    
    output reg                              data_sram_en,
    output reg [3 : 0]                      data_sram_wen,
    output reg [`REG_WIDTH - 1 : 0]         data_sram_addr,
    output reg [`REG_WIDTH - 1 : 0]         data_sram_wdata,
    input wire [`REG_WIDTH - 1 : 0]         data_sram_rdata,
    
    output reg [3 : 0]                      data2reg_en,
    output reg                              data2reg_signed,
    output reg [4 : 0]                      data2reg_shift,
    
    output reg                              div_stall,
    output reg                              data_stall
    
);
    
    reg pipeline_flush;
    always_ff @(posedge clk)
        if (rstn == `reset)                 pipeline_flush <= `false;
        else if (pipeline_flush_i)          pipeline_flush <= `true;
        else if (~ctrl_stall)               pipeline_flush <= `false;
    
    reg                              AdEL_exc;
    reg                              AdES_exc;
    reg                              Ov_exc;

    /* ************************ ALU cell related *********************** */
    
    reg [`PC_WIDTH - 1 : 0]         debug_pc;
    reg [`EXCCODE_WIDTH - 1 : 0]    exc_code;
    reg [`PC_WIDTH - 1 : 0]         error_pc;
    reg                             pc_in_delay_slot;
    
    reg [`REG_WIDTH - 1 : 0]        alu_op1;
    reg [`REG_WIDTH - 1 : 0]        alu_op2;
    reg [`ALUOP_WIDTH - 1 : 0]      alu_opcode;
    reg [`REG_ADDR_WIDTH - 1 : 0]   wregaddr;
    reg                             wregenable;
    
    always_ff @(posedge clk) begin
        if (rstn == `reset) begin
            alu_op1 <= `ZERO_REG;
            alu_op2 <= `ZERO_REG;
            alu_opcode <= 5'b00000;
            wregaddr <= 5'b00000;
            wregenable <= `false;
            debug_pc <= 32'hBFC00000;
            exc_code <= `Exc_None;
            error_pc <= 32'hBFC00000;
            pc_in_delay_slot <= `false;
        end else if (pipeline_flush) begin
            alu_op1 <= alu_op1;
            alu_op2 <= alu_op2;
            alu_opcode <= alu_opcode;
            wregaddr <= wregaddr;
            wregenable <= `false;
            debug_pc <= 32'hBFC00000;
            error_pc <= error_pc;
            exc_code <= exc_code;
            pc_in_delay_slot <= pc_in_delay_slot;
        end else if (ctrl_stall) begin
            alu_op1 <= alu_op1;
            alu_op2 <= alu_op2;
            alu_opcode <= alu_opcode;
            wregaddr <= wregaddr;
            wregenable <= wregenable;
            debug_pc <= debug_pc;
            error_pc <= error_pc;
            exc_code <= exc_code;
            pc_in_delay_slot <= pc_in_delay_slot;
        end else if (id_stalled) begin
            alu_op1 <= alu_op1;
            alu_op2 <= alu_op2;
            alu_opcode <= alu_opcode;
            wregaddr <= wregaddr;
            wregenable <= `false;
            debug_pc <= debug_pc;
            error_pc <= error_pc;
            exc_code <= exc_code;
            pc_in_delay_slot <= pc_in_delay_slot;
        end else begin
            alu_op1 <= opprand1_i;
            alu_op2 <= opprand2_i;
            alu_opcode <= aluop_i;
            wregaddr <= wregaddr_i;
            wregenable <= wregenable_i;
            debug_pc <= debug_pc_i;
            error_pc <= error_pc_i;
            exc_code <= exc_code_i;
            pc_in_delay_slot <= pc_in_delay_slot_i;
        end
    end
    
    reg is_first_cycle;
    always_ff @(posedge clk)
        if (rstn == `reset)
            is_first_cycle <= `false;
        else if (~(rstn == `reset | pipeline_flush | ctrl_stall | id_stalled))
            is_first_cycle <= `true;
        else if (is_first_cycle)
            is_first_cycle <= `false;
    
    always_comb     debug_pc_o = debug_pc;
    always_comb     pc_in_delay_slot_o = pc_in_delay_slot;
    
    wire [`REG_WIDTH - 1 : 0]       alu_rlt;
    wire                            alu_overflow;
    wire [`REG_WIDTH - 1 : 0]       alu_mult_hi;
    wire [`REG_WIDTH - 1 : 0]       alu_mult_lo;
    
    alu alu_cell (
        .op1_i      (alu_op1        ),
        .op2_i      (alu_op2        ),
        .aluop_i    (alu_opcode     ),
        .rlt_o      (alu_rlt        ),
        .overflow_o (alu_overflow   ),
        .whidata_o  (alu_mult_hi    ),
        .wlodata_o  (alu_mult_lo    )
    );
    
    // Ov detection
    always_comb Ov_exc = alu_overflow;
    
    /* ***************** Register write signal related **************** */
    
    always_comb begin
        wregdata_o <= alu_rlt;
        wregaddr_o <= wregaddr;
        wregenable_o <= ((AdEL_exc | Ov_exc) || exc_code != `Exc_None) ?`false : wregenable;
    end
    
    /* **************** Data read/write related *************** */
    
    reg [3 : 0]                     rdataen;
    reg                             rdatasigned;
    reg [3 : 0]                     wdataen;
    reg [`REG_WIDTH - 1 : 0]        wdata;
    
    always_ff @(posedge clk) begin
        if (rstn == `reset | pipeline_flush) begin
            rdataen <= 4'b0000;
            rdatasigned <= `false;
            wdataen <= 4'b0000;
            wdata <= `ZERO_REG;
        end else if (ctrl_stall | pipeline_flush) begin
            rdataen <= rdataen;
            rdatasigned <= rdatasigned;
            wdataen <= wdataen;
            wdata <= wdata;
        end else if (id_stalled) begin
            rdataen <= `false;
            rdatasigned <= `false;
            wdataen <= `false;
            wdata <= `ZERO_REG;
        end else begin
            rdataen <= rdataen_i;
            rdatasigned <= rdatasigned_i;
            wdataen <= wdataen_i;
            wdata <= wdata_i;
        end
    end
    
    // AdEL detection: Data read of Load instr
    always_comb
        AdEL_exc = (rdataen == 4'b0011 && alu_rlt[0]) | (rdataen == 4'b1111 && alu_rlt[1 : 0]);
    // AdES detection: Data write of Store instr
    always_comb
        AdES_exc = (wdataen == 4'b0011 && alu_rlt[0]) | (wdataen == 4'b1111 && alu_rlt[1 : 0]);
    
    reg [`REG_WIDTH - 1 : 0]        data_addr;
    reg [1 : 0]                     data_shift;
    always_comb begin
        data_addr = {alu_rlt[31 : 2], 2'b00};
        data_shift = alu_rlt[1 : 0];
    end
    
    always_comb begin
        data2reg_en = rdataen;
        data2reg_signed = rdatasigned;
        data2reg_shift = data_shift << 3;
    end
    
    always_comb begin
        data_sram_wen = (AdES_exc || exc_code != `Exc_None) ? 4'b0000 : wdataen << data_shift;
       // data_sram_addr = data_addr - 32'hA0000000;
        data_sram_addr = data_addr;
        data_sram_wdata = wdata << data2reg_shift;
    end
    
    reg read_enable;
    reg write_enable;
    //7.28 change 能两连续给出信号，不强制归0
    always_ff @(posedge clk)
        if (data_ok) begin
            read_enable <= `false;
            write_enable <= `false;
        end else if (~(rstn == `reset | pipeline_flush | ctrl_stall | id_stalled)) begin
            read_enable <= rdataen_i;
            write_enable <= wdataen_i;
        end else if (rstn == `reset) begin
            read_enable <= `false;
            write_enable <= `false;
        end
    
  
    always_comb data_sram_en = (|{read_enable, write_enable}) & (~data_ok);
    always_comb data_stall = (|{read_enable, write_enable}) & (~data_ok); 
    
    // always_comb data_o = data_sram_rdata;
    
    always_ff @(posedge clk)
        if (data_ok)        data_o <= data_sram_rdata;
    
    //always_comb data_sram_en = (|{rdataen, wdataen}) & (~data_ok);
    //always_comb data_stall = (|{rdataen, wdataen}) & (~data_ok); 
    
    /* ************************ DIV related *********************** */
    
    reg                         div_start;
    reg                         div_signed;
    reg [`REG_WIDTH - 1 : 0]    div_opprand1;
    reg [`REG_WIDTH - 1 : 0]    div_opprand2;
    wire [1 : 0]                div_status;
    wire [`REG_WIDTH - 1 : 0]   div_qutient;
    wire [`REG_WIDTH - 1 : 0]   div_remainder;
    
    reg signed_div;
    reg unsigned_div;
    always_ff @(posedge clk)
        if (div_status == `DIV_DONE) begin
            signed_div <= `false;
            unsigned_div <= `false;
        end else if (~(rstn == `reset | pipeline_flush | ctrl_stall | id_stalled)) begin
            signed_div <= (aluop_i == `DIV_ALU);
            unsigned_div <= (aluop_i == `DIVU_ALU);
        end else if (rstn == `reset) begin
            signed_div <= `false;
            unsigned_div <= `false;
        end
    
    // wire                        signed_div = (alu_opcode == `DIV_ALU);
    // wire                        unsigned_div = (alu_opcode == `DIVU_ALU);
    
    always_comb begin
        if (signed_div | unsigned_div)
            case (div_status)
                `DIV_AVA:   div_start = `true;
                default:    div_start = `false;
            endcase
        else                div_start = `false;
    end
    
    always_comb begin
        if (signed_div | unsigned_div)
            case (div_status)
                `DIV_AVA:   div_stall = `true;
                `DIV_RUN:   div_stall = `true;
                default:    div_stall = `false;
            endcase
        else                div_stall = `false;
    end
    
    always_comb div_signed = signed_div ? `true : `false;
    always_comb div_opprand1 = alu_op1;
    always_comb div_opprand2 = alu_op2;
    
    div div0(
        .clk            (clk            ),
        .rst            (rstn           ),
        
        .start_i        (div_start      ),
        .signed_i       (div_signed     ),
        .opprand1_i     (div_opprand1   ),
        .opprand2_i     (div_opprand2   ),
        .status_o       (div_status     ),
        .result_o       (div_qutient    ),
        .remainder_o    (div_remainder  )
    );
    
    /* ************************ HILO related *********************** */
    
    always_comb begin
        if ((alu_opcode == `DIV_ALU || alu_opcode == `DIVU_ALU) && div_status == `DIV_DONE) begin
            whireg_o = div_remainder;
            wloreg_o = div_qutient;
            whien_o = `true;
            wloen_o = `true;
        end else if (alu_opcode == `MUL_ALU || alu_opcode == `MULU_ALU) begin
            whireg_o = alu_mult_hi;
            wloreg_o = alu_mult_lo;
            whien_o = `true;
            wloen_o = `true;
        end else if ((id_whien_i | id_wloen_i) && (exc_code_i == `Exc_None)) begin
            whireg_o = id_whilodata_i;
            wloreg_o = id_whilodata_i;
            whien_o = id_whien_i;
            wloen_o = id_wloen_i;
        end else begin
            whireg_o = `ZERO_REG;
            wloreg_o = `ZERO_REG;
            whien_o = `false;
            wloen_o = `false;
        end
    end
    
    /* ********************* CP0 *************************** */
    reg                                cp0_wen;
    reg [`REG_ADDR_WIDTH - 1 : 0]      cp0_waddr;
    reg [`REG_WIDTH - 1 : 0]           cp0_wdata;
    
    always_ff @(posedge clk) begin
        if (rstn == `reset || pipeline_flush) begin
            cp0_wen <= `false;
            cp0_waddr <= 5'b00000;
            cp0_wdata <= 32'h0000_0000;
        end else if (ctrl_stall) begin
            cp0_wen <= cp0_wen;
            cp0_waddr <= cp0_waddr;
            cp0_wdata <= cp0_wdata;
        end else if (id_stalled) begin
            cp0_wen <= `false;
            cp0_waddr <= 5'b00000;
            cp0_wdata <= 32'h0000_0000;
        end else begin
            cp0_wen <= cp0_wen_i;
            cp0_waddr <= cp0_waddr_i;
            cp0_wdata <= cp0_wdata_i;
        end
    end
    
    always_comb begin
        cp0_wen_o = (exc_code != `Exc_None) ? `false : cp0_wen;
        cp0_waddr_o = cp0_waddr;
        cp0_wdata_o = cp0_wdata;
    end
    
    /* ********************* Exception ********************** */
    always_comb begin
        if (~is_first_cycle)                    exc_code_o = `Exc_None;
        else if (exc_code == `Exc_PCAdEL)       exc_code_o = `Exc_AdEL;
        else if (exc_code != `Exc_None)         exc_code_o =  exc_code;
        else if (AdEL_exc)                      exc_code_o = `Exc_AdEL;
        else if (AdES_exc)                      exc_code_o = `Exc_AdES;
        else if (Ov_exc)                        exc_code_o = `Exc_Ov;
        else                                    exc_code_o = `Exc_None;
    end
    
    always_comb begin
        if (exc_code == `Exc_PCAdEL)            badvaddr_o = error_pc;
        else if (AdEL_exc | AdES_exc)           badvaddr_o = alu_rlt;
        else                                    badvaddr_o = 32'h0000_0000;
    end

endmodule
